package com.heima.admin.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.heima.admin.mapper.ChannelMapper;
import com.heima.admin.mapper.SensitiveMapper;
import com.heima.admin.service.WemediaNewsAutoScanService;
import com.heima.aliyun.GreenImageScan;
import com.heima.aliyun.GreenTextScan;
import com.heima.common.constants.article.ArticleConstants;
import com.heima.common.constants.message.ArticleForEsConstants;
import com.heima.common.exception.CustomException;
import com.heima.feigns.article.ArticleFeign;
import com.heima.feigns.wemedia.WemediaFeign;
import com.heima.model.admin.pojo.AdChannel;
import com.heima.model.admin.pojo.AdSensitive;
import com.heima.model.article.dto.ArticleDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.wemedia.pojo.WmNews;
import com.heima.model.wemedia.pojo.WmUser;
import com.heima.utils.common.SensitiveWordUtil;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service//实现类上加
@Slf4j//方法长的话,各个环节打印日志  通过logback框架打印
public class WemediaNewsAutoScanServiceImpl implements WemediaNewsAutoScanService {

    //自媒体文章的自动审核
    @Autowired
    WemediaFeign wemediaFeign;

    @Value("${file.oss.web-site}")
    String website;

    @GlobalTransactional//分布式事务   全局事务
    @Override
    public void autoScanByMediaNewsId(Integer id) {
        //定时器   mq  调用  不是controller  调用   进入方法   告知方法   被调用
        log.info("=====自动发布文章方法被触发=====当前文章的id是=====>{}", id);
        //检查文章id
        if (id == null) {
            log.error("文章自动审核失败,id为空");//日志可以被看见
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID, "文章id为空");//后台看见
        }
        //根据文章id远程获取文章信息
        WmNews wmNews = wemediaFeign.findById(id);
        if (wmNews == null) {
            log.error("文章自动审核失败,远程查询自媒体文章失败");
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST, "对应的自媒体文章为空");
        }
        //检查文章的状态
        //如果文章状态为4或者8,且发布时间小于当前时间,调用发布方法    getCode   得到是值
        long now = System.currentTimeMillis();//时间的毫秒值
        if ((wmNews.getStatus().equals(WmNews.Status.ADMIN_SUCCESS.getCode()) || wmNews.getStatus().equals(WmNews.Status.SUCCESS.getCode())) && wmNews.getPublishTime().getTime() <= now) {//date类型
            //getTime  得到时间的毫秒值
            publishArticle(wmNews);//调用文章发布的方法

            log.info("自媒体文章被发布{}", id);//重要的环节上加上info
            return;//不需要向下
        }

        //如果文章状态为1   代表需要审核文章
        if (wmNews.getStatus().equals(WmNews.Status.SUBMIT.getCode())) {
            //抽取文章中的文本和图片

            //新建map集合

            //key:content  value:文本内容        key:images   value:  List<String> url   所有待审核的图片路径      封装得到的结果
            Map<String, Object> contentAndImages = handleTextAndImages(wmNews);

            //DFA  自管理  敏感词审核
            boolean isSensitive = handleSensetive((String) contentAndImages.get("content"), wmNews);
            if (!isSensitive)//为false
                return;//不执行下面的


            log.info("===当前处理的文章 通过敏感词DFA审核  id===>{}", id);

            //阿里云文本  审核
            boolean isTextScan = hanleTextScan((String) contentAndImages.get("content"), wmNews);
            if (!isTextScan)
                return;
            log.info("====当前处理的文章   通过阿里云的文章审核  id===>{}", id);

            //阿里云图片  审核

            boolean isImageScan = handleImageScan((List) contentAndImages.get("images"), wmNews);
            if (!isImageScan)
                return;

            log.info("====当前处理的文章   通过阿里云的图片审核  id===>", id);


            //如果发布时间  大于当前时间    状态为8
            if (wmNews.getPublishTime().getTime() > now) {
                updateWmNews(wmNews, (short) 8, "审核通过");
                return;//结束   不执行下面的内容
            }

            //调用发布文章的方法
            publishArticle(wmNews);

        }


    }

    /**
     * 阿里云图片审核
     *
     * @param images
     * @param wmNews
     * @return
     */
    @Autowired
    GreenImageScan greenImageScan;

    private boolean handleImageScan(List images, WmNews wmNews) {
        boolean flag = true;
        try {
            Map map = greenImageScan.imageUrlScan(images);
            String suggestion = (String) map.get("suggestion");
            if ("review".equals(suggestion)) {
                flag = false;
                updateWmNews(wmNews, (short) 3, "图片包含不确定因素,转为人工审核");
            }
            if ("block".equals(suggestion)) {
                flag = false;
                updateWmNews(wmNews, (short) 2, "图片违规");
            }
        } catch (Exception e) {
            e.printStackTrace();
            flag = false;
            updateWmNews(wmNews, (short) 3, "调用阿里云图片审核失败,转为人工审核");
        }
        return flag;
    }

    /**
     * 阿里云文本审核
     *
     * @param content
     * @param wmNews
     * @return
     */
    @Autowired
    GreenTextScan greenTextScan;

    private boolean hanleTextScan(String content, WmNews wmNews) {
        boolean flag = true;
        try {
            Map map = greenTextScan.greenTextScan(content);
            String suggestion = (String) map.get("suggestion");
            if ("review".equals(suggestion)) {
                flag = false;
                updateWmNews(wmNews, (short) 3, "内容中包含不确定的因素,转为人工审核");
            }
            if ("block".equals(suggestion)) {
                flag = false;
                updateWmNews(wmNews, (short) 2, "文本中包含敏感的词汇");
            }
        } catch (Exception e) {
            e.printStackTrace();
            flag = false;
            updateWmNews(wmNews, (short) 3, "阿里云审核失败,转为人工审核");
        }
        return flag;
    }

    @Autowired
    SensitiveMapper sensitiveMapper;

    /**
     * 自管理敏感词DFA审核
     * 审核失败   更改状态      修改文章状态为2     抽出来单独做一个方法     其他方法   可以一起调用
     *
     * @param content
     * @param wmNews
     * @return
     */
    private boolean handleSensetive(String content, WmNews wmNews) {
        boolean flag = true;//先默认为true
        List<AdSensitive> adSensitives = sensitiveMapper.selectList(null);//没有条件就是查询全部    都是从数据库中查   可以放到redis 缓存中
        //得到对象的集合
        //判断是否为空
        if (adSensitives == null || adSensitives.size() == 0) {
            return flag;//没有敏感词     放行
        }

        //敏感词内容列表
        List<String> sensitiveList = adSensitives.stream().map(AdSensitive::getSensitives).collect(Collectors.toList());//只能操作一个属性  映射   方法引用

        //转化为新的集合
        //基于DFA算法生成DFA数据模型(一个大map,套着一堆  小map)
        SensitiveWordUtil.initMap(sensitiveList);//不用var   内部集合是静态的
        //基于DFA算法进行敏感词的检测
        Map<String, Integer> sensitiveMap = SensitiveWordUtil.matchWords(content);//map 中的结果   key:出现的敏感词    value:出现的次数
        if (sensitiveList != null && sensitiveMap.size() > 0) {
            //有敏感词
            flag = false;
            updateWmNews(wmNews, (short) 2, "文章内容中包含敏感词:" + sensitiveMap);
            //更改文章的状态,2  表示  审核不通过
        }
        return flag;
    }


    //更改文章的状态
    private void updateWmNews(WmNews wmNews, short status, String reason) {
        wmNews.setStatus(status);
        wmNews.setReason(reason);
        //远程调用自媒体文章的接口
        ResponseResult responseResult = wemediaFeign.updateWmNews(wmNews);
        if (!responseResult.getCode().equals(0)) {
            log.error("远程修改 自媒体文章状态  失败 {}", responseResult.getErrorMessage());
            throw new CustomException(AppHttpCodeEnum.SERVER_ERROR, responseResult.getErrorMessage());//让事务回滚
        }


    }

    /**
     * 发布文章的方法
     *
     * @param wmNews
     */
    private void publishArticle(WmNews wmNews) {
        ResponseResult responseResult = saveApArticle(wmNews);
        if (!responseResult.getCode().equals(0)) {
            throw new CustomException(AppHttpCodeEnum.SERVER_ERROR, "远程保存文章失败");
        }
        wmNews.setArticleId((Long) responseResult.getData());//用户关联保存成功的article文章
        updateWmNews(wmNews, (short) 9, "发布成功");

        //远程通知  es  索引库   新增文章
        Map map = new HashMap();
        //mq  自动通知

        map.put("articleId",wmNews.getArticleId());
        kafkaTemplate.send(ArticleForEsConstants.ARTICLE_ELASTICSEARCH_ADD, JSON.toJSONString(map));
        log.info("文章已经自动发布，并发向搜索微服务发送 添加文章通知");

        //审核通过    通知kafka  修改索引库中文章的信息




    }


    @Autowired
    KafkaTemplate<String,String> kafkaTemplate;
    //消息的key是一个字符串   value也是



    //远程调用   保存文章

    /**
     * 远程调用文章的微服务
     * 保存文章的三表信息   article  article_config   article_content
     *
     * @param wmNews
     * @return
     */

    @Autowired
    ChannelMapper channelMapper;

    @Autowired
    ArticleFeign articleFeign;

    private ResponseResult saveApArticle(WmNews wmNews) {

        //封装请求参数对象   AritcleDto

        ArticleDto articleDto = new ArticleDto();

        BeanUtils.copyProperties(wmNews, articleDto);

        //拷贝时会将wmnews的id拷贝
        articleDto.setId(null);

        //有可能wmnews之间有article的id,有的话就是修改   没有的话就保存

        Long articleId = wmNews.getArticleId();
        if (articleId != null) {
            articleDto.setId(articleId);
        }
        //封面图片的布局
        articleDto.setLayout(wmNews.getType());

        articleDto.setFlag((byte) 0);//常规文章

        //设置作者信息  根据wmNews  关联的wmUser  查询出 wmUser 信息 (name  和作者表的 name  字段  是一致的)
        WmUser wmUser = wemediaFeign.findWmUserById(wmNews.getUserId());
        if (wmNews == null) {
            log.info("文章自动审核失败,获取关联自媒体的用户失败");
            throw new CustomException(AppHttpCodeEnum.SERVER_ERROR, "获取关联自媒体用户失败");
        }
        articleDto.setAuthorName(wmUser.getName());
        //设置频道信息
        AdChannel adChannel = channelMapper.selectById(wmNews.getChannelId());

        if (adChannel == null) {
            log.info("文章自动审核失败,获取关联频道失败");
            throw new CustomException(AppHttpCodeEnum.SERVER_ERROR, "获取关联频道失败");
        }

        articleDto.setChannelName(adChannel.getName());
        //频道id 属性已经copy过去

        //远程调用文章服务,保存文章三表信息
        return articleFeign.saveArticle(articleDto);
    }


    //抽取文章的内容和图片    图片的路径  需要接上前缀    交给阿里云  审核
    private Map<String, Object> handleTextAndImages(WmNews wmNews) {


        //自定义map
        Map<String, Object> result = new HashMap<>();//用来封装返回结果
        // "[ {type: image,  value: url},{type: text,  value: 内容1}{type: text,  value: 内容2}  ]"


        String wmNewsContent = wmNews.getContent();//得到内容的字符串

        //字符串  转成   list  集合
        if (StringUtils.isBlank(wmNewsContent)) {
            log.error("文章自动审核失败,自媒体文章的内容为空");
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID, "自媒体文章的内容为空");
        }
        //把Json字符串转化为
        List<Map> contentMap = JSONArray.parseArray(wmNewsContent, Map.class);//转化为泛型为map的list集合,用来转数组的方法  (对象是一个json数组),数组里面每一个元素是什么类型

        //哪些对象可以用steam流处理      一般用来处理集合   对集合进行处理
        String contentStr = contentMap.stream().filter(map -> "text".equals(map.get("type")))//过滤得到文本数据
                .map(map -> (String) map.get("value"))//取出文本的值   集合的元素  是  map
                .collect(Collectors.joining("_hmtt_"));//将文本的值拼接成字符串,中间用hmtt隔开,收集起来   拼接为完整的字符串   join  加符号  区分敏感词

        contentStr = contentStr + "_hmtt_" + wmNews.getTitle();//字符串拼接的比较少    多的话用stringBuffer   stringBuilder   拼接


        //抽取图片的内容
        //新建一个集合
        List<String> images = new ArrayList<>();

        //下面2个集合都可能为空

        contentMap.stream().filter(map -> "image".equals(map.get("type")))
                .map(map -> (String) map.get("value"))//内容中的图片是自动全路径的
                .forEach(images::add);//遍历   调对象的add方法   只用了这一个方法   入参是流里面的参数   url ->images.add(url)  方法引用
        //forEach也是结束句     内容,封面中图片可能为空   collect  不好操作   内容要抽取    封面图片也要抽取

        //判断封面图片是否空
        if (StringUtils.isNotBlank(wmNews.getImages())) {//xxx.jpg,aaa.jpg   按照逗号   切割完得到一个数组   得到每一个图   再添加个总的图片集合中
            //切割完后变成数组    基于数组   构建  流
            //数组工具类
            Arrays.stream(wmNews.getImages().split(","))//传入数组的参数    按照逗号  切割  得到对应的数组   基于数组构建出流
                    .map(url -> website + url)//拼接
                    .forEach(images::add);
        }

        //对集合中元素去重     内容和封面中图片可能存在重复

        //空指针   classnotfind 类找不到       数组下标越界        类型转换错误        0做被除数     文件找不到    常见的java异常


        //spring  IOC   AOP

        //AOP   面向切面编程     方法增强

        //IOC   控制反转     把对象创建交给spring管理    放到spring容器中


        //xxljob  基础架构    分布式调度架构     有一个调度中心


        //文章发布        文章审核      DFA   算法        阿里云图文       自动发布    定时发布     执行策略


        //发布时间   小于等于  当前时间

        images = images.stream().distinct().collect(Collectors.toList());

        result.put("content", contentStr);
        result.put("images", images);

        return result;
    }
}
